package online.inote.common.shiro;

import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.filter.DelegatingFilterProxy;

/**
 * <p>
 * Desc:
 * </p>
 *
 * @author XQF SUI
 * @created 2018年8月25日 下午6:47:18
 * @version 1.0
 */
@Configuration
@SuppressWarnings({ "rawtypes", "unchecked" })
public class ShiroConfigurer {

	private static final String COOKIE_NAME_KEY = "inote_cokie";
	private static final String SHIRO_REDIS_KEY_PREFIX = "inote_shiro_redis";
	
	@Bean
	public FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean() {
		FilterRegistrationBean<DelegatingFilterProxy> filterRegistration = new FilterRegistrationBean<DelegatingFilterProxy>();
		filterRegistration.setFilter(new DelegatingFilterProxy("shiroFilter"));
		filterRegistration.setEnabled(true);
		filterRegistration.addUrlPatterns("/*");
		filterRegistration.setDispatcherTypes(DispatcherType.REQUEST);
		return filterRegistration;
	}

	@Bean(name = "shiroFilter")
	public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, RedisTemplate redisTemplate) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager);

		setUrls(shiroFilterFactoryBean);
		configFilter(shiroFilterFactoryBean, redisTemplate);

		return shiroFilterFactoryBean;

	}

	private void setUrls(ShiroFilterFactoryBean shiroFilterFactoryBean) {
		// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
		shiroFilterFactoryBean.setLoginUrl("/user/jump/login");
		// 登录成功后要跳转的链接
		shiroFilterFactoryBean.setSuccessUrl("/index");
		// 未授权跳转界面
		shiroFilterFactoryBean.setUnauthorizedUrl("/error/401");
	}

	@Bean(name = "securityManager")
	public SecurityManager securityManager(RedisTemplate redisTemplate) {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		// 自定义realm
		securityManager.setRealm(myShiroRealm(redisTemplate));
		// 自定义session管理 使用redis
		securityManager.setSessionManager(sessionManager(redisTemplate));
		// 注入记住我管理器;
		securityManager.setRememberMeManager(rememberMeManager());

		return securityManager;
	}

	@Bean(name = "myShiroRealm")
	@DependsOn(value = { "lifecycleBeanPostProcessor", "ShiroRedisCacheManager" })
	public ShiroRealm myShiroRealm(RedisTemplate redisTemplate) {
		ShiroRealm shiroRealm = new ShiroRealm();
		shiroRealm.setCacheManager(redisCacheManager(redisTemplate));
		shiroRealm.setCachingEnabled(true);
		// 设置认证密码算法及迭代复杂度
		shiroRealm.setCredentialsMatcher(credentialsMatcher());
		// 认证
		shiroRealm.setAuthenticationCachingEnabled(false);
		// 授权
		shiroRealm.setAuthorizationCachingEnabled(false);
		return shiroRealm;
	}

	@Bean(name = "sessionManager")
	@DependsOn(value = "lifecycleBeanPostProcessor")
	public DefaultWebSessionManager sessionManager(RedisTemplate redisTemplate) {
		DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
		ShiroRedisSessionDao redisSessionDao = new ShiroRedisSessionDao(redisTemplate);
		// 这个name的作用也不大，只是有特色的cookie的名称。
		redisSessionDao.setSessionIdGenerator(sessionIdGenerator(COOKIE_NAME_KEY));
		sessionManager.setSessionDAO(redisSessionDao);
		sessionManager.setDeleteInvalidSessions(true);
		SimpleCookie cookie = new SimpleCookie();
		cookie.setName(COOKIE_NAME_KEY);
		sessionManager.setSessionIdCookie(cookie);
		sessionManager.setSessionIdCookieEnabled(true);
		return sessionManager;
	}

	/**
	 * <p>
	 * Desc: cookie管理对象;记住我功能
	 * </p>
	 *
	 * @author XQF SUI
	 * @created 2018年8月25日 下午9:23:28
	 * @return
	 */
	public CookieRememberMeManager rememberMeManager() {
		CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
		cookieRememberMeManager.setCookie(rememberMeCookie());
		// rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
		cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
		return cookieRememberMeManager;
	}

	@Bean(name = "ShiroRedisCacheManager")
	public ShiroRedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
		ShiroRedisCacheManager redisCacheManager = new ShiroRedisCacheManager(redisTemplate);
		// name是key的前缀，可以设置任何值，无影响，可以设置带项目特色的值
		redisCacheManager.createCache(SHIRO_REDIS_KEY_PREFIX);
		return redisCacheManager;
	}

	/**
	 * <p>
	 * Desc: realm的认证算法
	 * </p>
	 *
	 * @author XQF SUI
	 * @created 2018年8月25日 下午9:30:49
	 * @return
	 */
	@Bean(name = "hashedCredentialsMatcher")
	public HashedCredentialsMatcher credentialsMatcher() {
		HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher("md5");
		credentialsMatcher.setHashIterations(1);
		credentialsMatcher.setStoredCredentialsHexEncoded(true);
		return credentialsMatcher;
	}

	public ShiroSessionIdGenerator sessionIdGenerator(String name) {
		return new ShiroSessionIdGenerator(name);
	}

	/**
	 * 这个参数是RememberMecookie的名称，随便起。
	 * remenberMeCookie是一个实现了将用户名保存在客户端的一个cookie，与登陆时的cookie是两个simpleCookie。
	 * 登陆时会根据权限去匹配，如是user权限，则不会先去认证模块认证，而是先去搜索cookie中是否有rememberMeCookie，
	 * 如果存在该cookie，则可以绕过认证模块，直接寻找授权模块获取角色权限信息。 如果权限是authc,则仍会跳转到登陆页面去进行登陆认证.
	 * 
	 * @return
	 */
	public SimpleCookie rememberMeCookie() {
		SimpleCookie simpleCookie = new SimpleCookie("remenbermeCookie");
		// <!-- 记住我cookie生效时间30天 ,单位秒;-->
		simpleCookie.setMaxAge(60);
		return simpleCookie;
	}

	/**
	 * <p>
	 * Desc: 配置拦截器
	 * </p>
	 *
	 * @author Sui
	 * @created 2017年12月3日 下午10:34:53
	 * @param shiroFilterFactoryBean
	 */
	public void configFilter(ShiroFilterFactoryBean shiroFilterFactoryBean, RedisTemplate redisTemplate) {
		// 拦截器
		Map<String, String> filtersMap = new LinkedHashMap<String, String>();

		configNotIntercepted(filtersMap);
		configLogout(filtersMap);
		configIntercepted(filtersMap);

		Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
		filters.put("logout", new ShiroLogoutFilter(redisTemplate));
		shiroFilterFactoryBean.setFilters(filters);

		shiroFilterFactoryBean.setFilterChainDefinitionMap(filtersMap);
	}

	/**
	 * <p>
	 * Desc: 配置不被拦截的链接
	 * </p>
	 *
	 * @author Sui
	 * @created 2017年12月3日 下午10:35:49
	 * @param filterMap
	 */
	public void configNotIntercepted(Map<String, String> filterMap) {
		filterMap.put("/", "anon");
		filterMap.put("/resources/**", "anon");
		filterMap.put("/user/login", "anon");
		filterMap.put("/user/signIn", "anon");
		filterMap.put("/captcha", "anon");
		filterMap.put("/article/list/*", "anon");
		filterMap.put("/article/flow", "anon");
		filterMap.put("/article/jump/**", "anon");
		filterMap.put("/article/view/*", "anon");
	}

	/**
	 * <p>
	 * Desc: 配置被拦截的链接
	 * </p>
	 *
	 * @author Sui
	 * @created 2017年12月3日 下午10:38:46
	 * @param filterMap
	 */
	public void configIntercepted(Map<String, String> filterMap) {
		filterMap.put("/**", "authc"); // authc:所有url都必须认证通过才可以访问
	}

	/**
	 * <p>
	 * Desc: 配置退出
	 * </p>
	 *
	 * @author Sui
	 * @created 2017年12月3日 下午10:37:35
	 */
	public void configLogout(Map<String, String> filterMap) {
		filterMap.put("/logout", "logout");
	}

	@Bean(name = "lifecycleBeanPostProcessor")
	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
		return new LifecycleBeanPostProcessor();
	}
}